<?php

declare(strict_types=1);
/**
 * This file is part of Hyperf.
 *
 * @link     https://www.hyperf.io
 * @document https://hyperf.wiki
 * @contact  group@hyperf.io
 * @license  https://github.com/hyperf/hyperf/blob/master/LICENSE
 */
namespace App\Utils;

use Hyperf\Elasticsearch\ClientBuilderFactory;

class Elasticsearch
{
    /**
     * @var ContainerInterface
     */
    protected $container;

    protected $esClient;

    /**
     * 实例化客户端对象
     */
    public function __construct()
    {
        $this->container = container();

        $clientBuilder = $this->container->get(ClientBuilderFactory::class);

        $builder = $clientBuilder->create();

        $this->esClient = $builder->setHosts(['http://127.0.0.1:9200'])->build();
    }

    /**
     * 判断索引是否存在.
     *
     * @param mixed $index
     */
    public function indexExistsEs($index)
    {
        $params = [
            'index' => $index,
        ];

        return $this->esClient->indices()->exists($params);
    }

    /**
     * 创建索引.
     *
     * @param mixed $index
     */
    public function createIndex($index)
    {
        $params = [
            'index' => $index,
        ];

        return $this->esClient->indices()->create($params);
    }

    /**
     * 设置mapping.
     * @param $index
     * @param $type
     * @param $mappingKey
     * @return mixed
     */
    public function putMapping($index, $type, $mappingKey)
    {
        $mapping['index'] = $index;

        //$mapping['type'] = $type;

        $field_type['keyword'] = [
            'type' => 'keyword',
        ];

        $field_type['text'] = [
            'type' => 'text',

            'analyzer' => 'ik_max_word',

            'search_analyzer' => 'ik_max_word',
        ];

        $data = [
            'properties' => value(function () use ($mappingKey, $field_type) {
                $properties = [];

                foreach ($mappingKey as $key => $value) {
                    if (empty($value)) {
                        continue;
                    }

                    foreach ($value as $cvalue) {
                        $properties[$cvalue] = $field_type[$key];
                    }
                }

                return $properties;
            }),
        ];
        $mapping['body'] = $data;
        return $this->esClient->indices()->putMapping($mapping);
    }

    /**
     * 判断文档是否存在.
     *
     * @param mixed $params
     */
    public function existsEs($params)
    {
        return $this->esClient->exists($params);
    }

    /**
     * 创建文档.
     *
     * @param mixed $index
     * @param mixed $id
     * @param mixed $data
     */
    public function indexEs($index, $id, $data)
    {
        $index_data = [
            'index' => $index,

            //            'type' => $type,

            'id' => $id,

            'body' => $data,
        ];

        return $this->esClient->index($index_data);
    }

    /**
     * 修改文档.
     *
     * @param mixed $index
     * @param mixed $id
     * @param mixed $data
     */
    public function updateEs($index, $id, $data)
    {
        $update_data = [
            'index' => $index,

            //'type' => $type,

            'id' => $id,

            'body' => [
                'doc' => $data,
            ],
        ];

        return $this->esClient->update($update_data);
    }

    /**
     * 删除文档.
     *
     * @param mixed $params
     * @param mixed $index
     * @param mixed $id
     */
    public function deleteEs($index, $id)
    {
        $delete_data = [
            'index' => $index,

            //            'type' => $type,

            'id' => $id,
        ];

        return $this->esClient->delete($delete_data);
    }

    /**
     * 查询数据.
     *
     * 请求参数实例：
     *
     * $data: 详细数据 键值对形式
     *
     * $condition['must_field']: es bool查询must对应字段 ['terms_field'=>['id','pid'],'range_field'=>['ctime','age']] 等
     *
     * $condition['should_field']: es bool查询should对应字段 ['terms_field'=>['id','pid'],'range_field'=>['ctime','age']] 等
     *
     * $source_field: es 查询需要获取的字段
     *
     * $field_alias:  查询字段别名 例如 'field'=>[{'a'=>'123'}] 查询时候拼接 field.a=*
     *
     * @param mixed $index
     * @param mixed $data
     * @param mixed $condition
     * @param mixed $sourceField
     * @param mixed $fieldAlias
     */
    public function searchEs($index, $data, $condition, $sourceField, $fieldAlias)
    {
        if (! isset($fieldAlias)) {
            $fieldAlias = [];
        }

        $offset = $data['offset'] ?? 0;

        $limit = $data['limit'] ?? 50;

        $orderField = $data['order_field'] ?? 'id';

        $orderType = $data['order_type'] ?? 'desc';

        if (! in_array($orderType, ['asc', 'desc'])) {
            $orderType = 'desc';
        }

        //初始化查询body

        $body = ['from' => $offset, 'size' => $limit, 'sort' => [$orderField => $orderType]];

        //获取bool查询条件

        $bool = $this->_getQueryInfo($data, $condition, $fieldAlias);

        if (! empty($bool)) {
            $body['query'] = ['bool' => $bool];
        }

        if (! empty($sourceField)) {
            $body['_source'] = explode(',', $sourceField);
        }

        $params = [
            'index' => $index,

            //'type' => $type,

            'body' => $body,
        ];

        return $this->esClient->search($params);
    }

    /**
     * 查询数据.
     *
     * 请求参数实例：
     *
     * $es_params['data']: 详细数据 键值对形式
     *
     * $es_params['must_field']: es bool查询must对应字段 ['terms_field'=>['id','pid'],'range_field'=>['ctime','age']] 等
     *
     * $es_params['should_field']: es bool查询should对应字段 ['terms_field'=>['id','pid'],'range_field'=>['ctime','age']] 等
     *
     * $es_params['source_field']: es 查询需要获取的字段
     *
     * $es_params['field_alias']:  查询字段别名 例如 ['a'=>['b','c','d']] 查询时候拼接 a.b=* a.c=*
     *
     * @param mixed $es_params
     * @param mixed $esParams
     */
    public function newSearchEs($esParams)
    {
        $index = $this->extractArgument($esParams, 'index');
        $fieldAlias = $this->extractArgument($esParams, 'field_alias');
        $data = $this->extractArgument($esParams, 'data');
        $mustField = $this->extractArgument($esParams, 'must_field');
        $sourceField = $this->extractArgument($esParams, 'source_field');
        $shouldField = $this->extractArgument($esParams, 'should_field');

        if (! isset($fieldAlias)) {
            $fieldAlias = [];
        }

        $offset = $data['offset'] ?? 0;

        $limit = $data['limit'] ?? 50;

        $bool = [];

        if (isset($mustField)) {
            $mustCondition = $this->_getCondition($data, $mustField, $fieldAlias);

            if (! empty($mustCondition)) {
                $bool['must'] = $mustCondition;
            }
        }

        if (isset($shouldField)) {
            $shouldCondition = $this->_getCondition($data, $shouldField, $fieldAlias);

            if (! empty($shouldCondition)) {
                $bool['should'] = $shouldCondition;

                $bool['minimum_should_match'] = 1;
            }
        }

        $body = ['from' => $offset, 'size' => $limit];

        if (! empty($bool)) {
            $body['query'] = ['bool' => $bool];
        }

        if (! empty($sourceField)) {
            $body['_source'] = $sourceField;
        }

        $params = [
            'index' => $index,

            //'type' => $type,

            'body' => $body,
        ];

        return $this->esClient->search($params);
    }

    /**
     * @return null|mixed
     */
    public function extractArgument(array &$params, string $arg)
    {
        if (array_key_exists($arg, $params) === true) {
            $val = $params[$arg];
            unset($params[$arg]);
            return $val;
        }
        return null;
    }

//    public function updateByQueryEs($params)
//    {
//        extract($params);
//
//        $query = $this->_getQueryInfo($condition_data, $condition, $field_alias);
//
//        $script = ['params' => $data, 'lang' => 'painless'];
//
//        $source = [];
//
//        foreach ($data as $key => $value) {
//            $source[] = 'ctx._source.' . $key . '=' . 'params.' . $key;
//        }
//
//        $script['source'] = implode(';', $source);
//
//        $body = ['script' => $script, 'query' => $query];
//
//        $update_data = [
//            'index' => $index,
//
//            'type' => $type,
//
//            'body' => $body,
//        ];
//
//        return $this->esClient->updateByQuery($update_data);
//    }

//    public function deleteByQueryEs($params)
//    {
//        extract($params);
//
//        $query = $this->_getQueryInfo($condition_data, $condition, $field_alias);
//
//        $body = ['query' => $query];
//
//        $delete_data = [
//            'index' => $index,
//
//            'type' => $type,
//
//            'body' => $body,
//        ];
//
//        return $this->esClient->deleteByQuery($delete_data);
//    }

    /**
     * 整理bool查询条件.
     *
     * 请求参数实例：
     *
     * $es_params['data']: 详细数据 键值对形式
     *
     * $condition['must_field']: es bool查询must对应字段 ['terms_field'=>['id','pid'],'range_field'=>['ctime','age']] 等
     *
     * $condition['should_field']: es bool查询should对应字段 ['terms_field'=>['id','pid'],'range_field'=>['ctime','age']] 等
     *
     * $field_alias:  查询字段别名 例如 ['a'=>['b','c','d']] 查询时候拼接 a.b=* a.c=*
     *
     * @param mixed $data
     * @param mixed $condition
     * @param mixed $field_alias
     */
    private function _getQueryInfo($data, $condition, $field_alias)
    {
        extract($condition);

        $bool = [];

        if (isset($must_field)) {
            $must_condition = $this->_getCondition($data, $must_field, $field_alias);

            if (! empty($must_condition)) {
                $bool['must'] = $must_condition;
            }
        }

        if (isset($should_field)) {
            $should_condition = $this->_getCondition($data, $should_field, $field_alias);

            if (! empty($should_condition)) {
                $bool['should'] = $should_condition;

                $bool['minimum_should_match'] = 1;
            }
        }

        return $bool;
    }

    private function _getCondition($data, $condition_field, $field_alias = [])
    {
        extract($condition_field);

        $condition = [];

        foreach ($data as $key => &$value) {
            $middle_key = $key;

            if (! empty($field_alias)) {
                foreach ($field_alias as $alias_key => $alias_value) {
                    if (in_array($key, $alias_value)) {
                        $middle_key = $alias_key . '.' . $key;

                        break;
                    }
                }
            }

            if (isset($terms_field) && in_array($key, $terms_field)) {
                if (is_int($value)) {
                    $value = (string) $value;
                }

                $condition[] = ['terms' => [$middle_key => explode(',', $value)]];

                continue;
            }

            if (isset($range_field) && in_array($key, $range_field)) {
                [$from, $to] = $value;

                if ($from == 0) {
                    $condition[] = ['range' => [$middle_key => ['lte' => $to]]];
                } elseif ($to == 0) {
                    $condition[] = ['range' => [$middle_key => ['gte' => $from]]];
                } else {
                    $condition[] = ['range' => [$middle_key => ['gte' => $from, 'lte' => $to]]];
                }

                continue;
            }

            if (isset($match_field) && in_array($key, $match_field)) {
                $condition[] = ['match' => [$middle_key => $value]];
            }
        }

        return $condition;
    }
}
